package server.communication;

import java.io.IOException;
import java.net.Socket;
import java.util.LinkedList;
import java.util.concurrent.Semaphore;

import plugin.client.ClientPlugin.OS;
import server.communication.ClientMessages.ClientDetailsMessageThread;
import server.communication.ClientMessages.ClientMessageThread;
import server.communication.ClientMessages.ExecuteBashCommandMessageThread;
import server.communication.ClientMessages.ExecuteCommandMessageThread;
import server.communication.ClientMessages.PingMessageThread;
import server.plugins.ClientPluginManager;

public class Client{

	private Socket socket;
	private MessageProcessor mesProc = new MessageProcessor(this);
	
	private LinkedList <ClientMessageThread> messageThreads = new LinkedList<ClientMessageThread>();
	private Semaphore messageThreadsSem = new Semaphore (1);
	private Semaphore osNameSem = new Semaphore (1);
	private String osName = null;
	private Semaphore osVersionSem = new Semaphore(1);
	private String osVersion = null;
	private Semaphore architectureSem = new Semaphore(1);
	private String architecture = null;
	private Semaphore hostNameSem = new Semaphore(1);
	private String hostName = null;
	private Semaphore hostAddressSem = new Semaphore(1);
	private String hostAddress = null;
	private Semaphore clientVersionSem = new Semaphore(1);
	private float clientVersion = 0;
	private String [] installedPlugins = null;
	private Semaphore installedPluginsSem = new Semaphore(1);
	private String loggetUser = null;
	private Semaphore loggedUserSem = new Semaphore(1);
	private String macAddress = null;
	private Semaphore macAddressSem = new Semaphore(1);
	
	public Client (Socket socket) throws Exception
	{
		this.socket = socket;
		socket.setSoTimeout(10000);
		socket.setKeepAlive(true);
		askAboutClientDetails();
		ping();
	}
	
	public void pauseAllMessages()
	{
		for (ClientMessageThread t : messageThreads)
			t.pause();
	}
	
	public void pauseAllMessages(Thread but)
	{
		for (ClientMessageThread t : messageThreads)
			if (t != but)
				t.pause();
	}
	
	public void resumeMessages()
	{
		for (ClientMessageThread t : messageThreads)
			if (t.isPaused())
				t.Resume();
	}
	
	public void removeMessageThread(Thread t)
	{
		try
		{
			messageThreadsSem.acquire();
			messageThreads.remove(t);
		}
		catch (Exception e){System.err.println("Unable to remove message thread"); }
		messageThreadsSem.release();
	}
	
	public int aliveMessageThreads()
	{
		int c = 0;
		try
		{
			messageThreadsSem.acquire();
			for (Thread t : messageThreads)
				if (t.isAlive() && !(t instanceof PingMessageThread))
					c++;
		}
		catch (Exception e){System.err.println("Unable to remove message thread"); }
		messageThreadsSem.release();
		return c;
	}
	
	public void stop()
	{
		try
		{
			socket.close();
			messageThreadsSem.acquire();
			for (Thread t : messageThreads)
				t.stop();
			messageThreads.clear();
		}
		catch (Exception e){System.err.println("Unable to remo message thread"); }
		messageThreadsSem.release();
	}
	
	public MessageProcessor getMessageProcessor()
	{
		return mesProc;
	}
	
	public void addMessageThread(ClientMessageThread t)
	{
		try
		{
			messageThreadsSem.acquire();
			messageThreads.add(t);
		}
		catch (Exception e){System.err.println("Unable to add message thread"); }
		messageThreadsSem.release();
	}
	
	public Socket getSocket()
	{
		return socket;
	}
	
	private void askAboutClientDetails()
	{
		ClientDetailsMessageThread t = null;
		try
		{
			t = new ClientDetailsMessageThread(this);
		}
		catch(IOException e)
		{
			System.err.println("Unable to get client details");
		}
		this.addMessageThread(t);
		t.start();
	}
	public void executeCommand(String command, Socket root)
	{
		ExecuteCommandMessageThread t = null;
		try
		{
			t = new ExecuteCommandMessageThread(this, root, command);
		}
		catch(Exception e)
		{
			System.err.println("Unable to execute command");
			e.printStackTrace();
		}
		this.pauseAllMessages();
		this.addMessageThread(t);
		t.start();
	}
	
	public void executeBashCommand(String command, Socket root)
	{
		ExecuteBashCommandMessageThread t = null;
		try
		{
			t = new ExecuteBashCommandMessageThread(this, root, command);
		}
		catch(Exception e)
		{
			System.err.println("Unable to execute command");
			e.printStackTrace();
		}
		this.pauseAllMessages();
		this.addMessageThread(t);
		t.start();
	}
	
	
	public String [] getInstalledPlugins()
	{
		String [] s = null;
		try
		{
			this.installedPluginsSem.acquire();
			s = this.installedPlugins;
		}
		catch(Exception e){}
		this.installedPluginsSem.release();
		return s;
	}
	public void setInstalledPlugins(String [] s)
	{
		try
		{
			this.installedPluginsSem.acquire();
			this.installedPlugins = s;
		}
		catch(Exception e){}
		this.installedPluginsSem.release();
	}
	public String getOSName()
	{
		String s = null;
		try
		{
			this.osNameSem.acquire();
			s = this.osName;
		}
		catch(Exception e){}
		this.osNameSem.release();
		return s;
	}
	public void setOSName(String s)
	{
		try
		{
			this.osNameSem.acquire();
			this.osName = s;
		}
		catch(Exception e){}
		this.osNameSem.release();
	}
	
	public String getOSVersion()
	{
		String s = null;
		try
		{
			this.osVersionSem.acquire();
			s = this.osVersion;
		}
		catch(Exception e){}
		this.osVersionSem.release();
		return s;
	}
	
	public void setOSVersion(String s)
	{
		try
		{
			this.osVersionSem.acquire();
			this.osVersion = s;
		}
		catch(Exception e){}
		this.osVersionSem.release();
	}
	public String getUserName()
	{
		String s = null;
		try
		{
			this.loggedUserSem.acquire();
			s = this.loggetUser;
		}
		catch(Exception e){}
		this.loggedUserSem.release();
		return s;
	}
	
	public void setUserName(String s)
	{
		try
		{
			this.loggedUserSem.acquire();
			this.loggetUser = s;
		}
		catch(Exception e){}
		this.loggedUserSem.release();
	}
	public String getArchitecture()
	{
		String s = null;
		try
		{
			this.architectureSem.acquire();
			s = this.architecture;
		}
		catch(Exception e){}
		this.architectureSem.release();
		return s;
	}
	public void setArchitecture(String s)
	{
		try
		{
			this.architectureSem.acquire();
			this.architecture = s;
		}
		catch(Exception e){}
		this.architectureSem.release();
	}
	public String getHostName()
	{
		String s = null;
		try
		{
			this.hostNameSem.acquire();
			s = this.hostName;
		}
		catch(Exception e){}
		this.hostNameSem.release();
		return s;
	}
	public void setHostName(String s)
	{
		try
		{
			this.hostNameSem.acquire();
			this.hostName = s;
		}
		catch(Exception e){}
		this.hostNameSem.release();
	}
	public String getHostAddress()
	{
		String s = null;
		try
		{
			this.hostAddressSem.acquire();
			s = this.hostAddress;
		}
		catch(Exception e){}
		this.hostAddressSem.release();
		return s;
	}
	public void setHostAddress(String s)
	{
		try
		{
			this.hostAddressSem.acquire();
			this.hostAddress = s;
		}
		catch(Exception e){}
		this.hostAddressSem.release();
	}
	public float getClientVersion()
	{
		float s = -1;
		try
		{
			this.clientVersionSem.acquire();
			s = this.clientVersion;
		}
		catch(Exception e){}
		this.clientVersionSem.release();
		return s;
	}
	public void setClientVersion(float s)
	{
		try
		{
			this.clientVersionSem.acquire();
			this.clientVersion = s;
		}
		catch(Exception e){}
		this.clientVersionSem.release();
	}
	public String getMacAddress()
	{
		String s = null;
		try
		{
			this.macAddressSem.acquire();
			s = this.macAddress;
		}
		catch(Exception e){}
		this.macAddressSem.release();
		return s;
	}
	
	public void setMacAddress(String s)
	{
		try
		{
			this.macAddressSem.acquire();
			this.macAddress = s;
		}
		catch(Exception e){}
		this.macAddressSem.release();
	}
	
	private void ping()
	{
		PingMessageThread t = null;
		try
		{
			t = new PingMessageThread(this);
		}
		catch(IOException e)
		{
			return;
		}
		this.addMessageThread(t);
		t.start();
	}
	
	public String [] getNotInstalledPlugins(String [] installedPlugins)
	{
		OS os;
		String system = this.getOSName();
		if (system.contains("Windows"))
			os = OS.Windows;
		else if (system.contains("Linux") || system.contains("Unix"))
			os = OS.Linux;
		else if (system.contains("Mac"))
			os = OS.Mac;
		else
		{
			System.err.println("Unrecognised OS");
			os = OS.All;
		}
		String [] allPlugins = ClientPluginManager.getNames_Versions(os);
		if (installedPlugins == null)
			return ClientPluginManager.getNames(os);
		
		boolean test;
		int counter = 0;
		for (String plugin : allPlugins)
		{
			test = false;
			for (String instPlugin : installedPlugins)
			{
				if (plugin.equals(instPlugin))
				{
					test = true;
					break;
				}
			}
			if (!test)
				counter++;
		}
		String [] toInstall = new String[counter];
		counter = 0;
		for (String plugin : allPlugins)
		{
			test = false;
			for (String instPlugin : installedPlugins)
			{
				if (plugin.equals(instPlugin))
				{
					test = true;
					break;
				}
			}
			if (!test)
			{
				int pos = plugin.lastIndexOf("_");
				toInstall[counter++] = plugin.substring(0, pos);
			}
		}
		return toInstall;
	}
}
